import random
import math
from xml.etree import ElementTree

import actions

import pyglet
from pyglet.gl import *

import cocos
from cocos.actions import *


# XXX need to add some check to see whether
# a character can use/wear an item (eg. needing N strength, N dex, X skill)

# No limitations to be enforced about what *classes* can use/wear

def count_duplicates(items):
    items.sort()
    last = (None, None)
    l = []
    for item in items:
        if item == last[0]:
            last[1] += 1
        else:
            last = [item, 1]
            l.append(last)
    return l

class Item(object):
    '''An item that the characters may own

    Items have several properties:

      type          -- for maybe grouping
      name          -- (type, name) must be unique
      label         -- for the action selector (popup menu at present)
      description   -- maybe a tooltip thing
      cost          -- amount of coins this item costs
      weight        -- weight in kilograms (integer only)
      quality       -- between 0 (broken) and 1 (new) (or higher for
                        exceptional)
      magic_bonus   -- between 0 and however generous we feel :)
      two_handed    -- whether this requires both hands
      skill         -- what skill is require to get full use of this item
      armour_value  -- what armour value this item contributes
      sprite_name   -- the sprite filename to use for this item

    Items are considered to be the same if their (type, name, quality,
    magic_bonus) are equal.
    '''

    skill = None
    armour_value = 0

    qualities = {
        1.25: 'Exceptional',
        1: 'New',
        .75: 'Second-Hand',
        .5: 'Worn',
        .25: 'Broken',
    }

    def __init__(self, quality=1, magic_bonus=0):
        self.quality = quality
        self.magic_bonus = magic_bonus

        if quality != 1:
            self.label = '%s %s'%(self.qualities[quality], self.label)

        if magic_bonus != 0:
            sign = '-+'[magic_bonus > 0]
            self.label = '%s%d %s %s'%(sign, magic_bonus, self.label)

        # account for quality
        self.armour_value = int(quality * self.armour_value)

        # exceptional quality items weigh 10% less
        if quality > 1:
            self.weight = int(.9 * self.weight)

        # exception quality items cost 2x
        # (the rest are just cheaper)
        if quality > 1:
            quality = 2
        self.cost = int(quality * self.cost)


    def get_action_options(self):
        '''Returns Action instances for using this item.

        Returns [] if it's not usable.
        '''
        return []

    def __cmp__(self, other):
        if not isinstance(other, Item):
            # default Pythonic comparison
            return cmp(self, other)
        n = cmp(self.type, other.type)
        if n: return n
        n = cmp(self.name, other.name)
        if n: return n
        n = cmp(self.quality, other.quality)
        if n: return n
        return cmp(self.magic_bonus, other.magic_bonus)

    def __eq__(self, other):
        if not isinstance(other, Item):
            return False
        return (self.type == other.type
            and self.name == other.name
            and self.quality == other.quality
            and self.magic_bonus == other.magic_bonus)

    def __repr__(self):
        return '<%s:%s q=%s m=%s>'%(self.type, self.name, self.quality, self.magic_bonus)

    def to_xml(self, root):
        m = ElementTree.SubElement(root, 'item', type=self.type,
            name=self.name, quality=str(self.quality),
            magic_bonus=str(self.magic_bonus))
        m.tail = '\n'


def from_xml(tag):
    '''Load the item from an XML file.
    '''
    type = tag.get('type')
    name = tag.get('name')
    quality = float(tag.get('quality'))
    magic_bonus = int(tag.get('magic_bonus'))
    return items[type][name](quality, magic_bonus)

items = {}
itemlist = []
def add_item(klass):
    items.setdefault(klass.type, {})[klass.name] = klass
    itemlist.append(klass)

#
# WEAPONS
#
class Weapon(Item):
    '''Weapons do damage and thus have a damage() method.
    '''

    def damage(self, character, other):
        '''Calculate damage done by this weapon.

        The damage amount uses:

            * Weapon's damage range (no bounds but giving an integer number)
            * Weapon quality (0.25 to 1.25)
            * Attacker's strength (0 to 10) - 5 strength is required to get the average damage
            * Weapon's magic bonus (no bounds)

            damage = random value in a range
            damage = damage * quality
            damage = damage * (.5 + strength / 10)
            damage = damage + magic bonus

        Base weapon damage is a range between damage_range_low and damage_range_high
        **inclusive**.

        This is then modified by the attacker's strength. The base damage
        requires a strength of 5 (the average). Any less will reduce the damage
        and any more will increase it.
        '''
        damage = random.randint(self.damage_range_low, self.damage_range_high + 1)
        damage *= self.quality
        damage *= (.5 + character.strength / 10.)
        return int(damage) + self.magic_bonus

    def animate_attack(self, layer, attack):
        i, j = attack.other.position
        x, y = attack.level.ground.get_cell(i, j).center
        i = pyglet.resource.image(attack.weapon.sprite_name)

        s = cocos.sprite.Sprite(i, (x, y), scale=2)
        layer.add(s)
        anim = (
            AccelDeccel(Rotate(-45, .25) + Rotate(45, .25)) |
            AccelDeccel(MoveBy((0, 32), .25) + MoveBy((0, -32), .25))
        )
        s.do(anim + CallFunc(layer.done))

    def get_action_options(self):
        return [self.action_class(self)]

class MeleeWeapon(Weapon):
    action_class = actions.MeleeAttack

class Sword(MeleeWeapon):
    slot = 'right_hand'
    type = 'sword'
    name = 'sword'
    label = 'Sword'
    cost = 10
    weight = 5
    description = 'Your basic long pointy metal with handle.'
    action_cost = 3
    sprite_name = 'item-sword.png'
    damage_range_low = 4
    damage_range_high = 9
add_item(Sword)

class Dagger(MeleeWeapon):
    slot = 'right_hand'
    type = 'dagger'
    name = 'dagger'
    label = 'Dagger'
    cost = 5
    weight = 2
    description = 'Your basic short pointy metal with handle.'
    action_cost = 3
    sprite_name = 'item-sword.png' # XXX need sword
    damage_range_low = 2
    damage_range_high = 6
add_item(Dagger)

class RangedWeapon(Weapon):
    def animate_attack(self, layer, attack):
        i1, j1 = attack.character.position
        x1, y1 = attack.level.ground.get_cell(i1, j1).center

        i2, j2 = attack.other.position
        x2, y2 = attack.level.ground.get_cell(i2, j2).center

        angle = math.degrees(math.atan2(y1-y2, x2-x1))

        i = pyglet.resource.image(attack.weapon.ammo_sprite_name)
        s = cocos.sprite.Sprite(i, (x1, y1), rotation=angle)
        layer.add(s)

        # need constant-speed 1024 pixels per second not constant-time animation here
        distance = math.sqrt((x2-x1)**2 + (y2-y1)**2)
        t = distance / 1024.
        s.do(MoveTo((x2, y2), t) + CallFunc(layer.done))

class Bow(RangedWeapon):
    slot = 'right_hand'
    type = 'bow'
    name = 'bow'
    label = 'Bow'
    description = 'A stick of wood bent by a string.'
    two_handed = True
    cost = 20
    weight = 4
    action_cost = 3
    sprite_name = 'item-bow.png'
    ammo_sprite_name = 'ammo-arrow.png'
    action_class = actions.RangedAttack
    damage_range_low = 3
    damage_range_high = 6
add_item(Bow)

class Staff(MeleeWeapon):
    slot = 'right_hand'
    type = 'staff'
    name = 'staff'
    label = 'Staff'
    description = 'A worn wizard staff, handed down through generations'
    two_handed = True
    cost = 8
    weight = 5
    action_cost = 4
    sprite_name = 'item-staff.png'
    action_class = actions.MeleeAttack
    damage_range_low = 3
    damage_range_high = 6
add_item(Staff)

class Mace(MeleeWeapon):
    slot = 'right_hand'
    type = 'mace'
    name = 'mace'
    label = 'Mace'
    description = 'Ball mace. Cracks skulls.'
    cost = 12
    weight = 8
    action_cost = 4
    sprite_name = 'item-mace.png'
    action_class = actions.MeleeAttack
    damage_range_low = 4
    damage_range_high = 8
add_item(Mace)


#
# ARMOUR
#
# 10 is the maximum value for any one piece
# full PLATE suit is worth 20 points - higher is possible with magic
#
# XXX PLEASE CHECK VALUES AND BALANCE ME
#
# the chance that someone will hit you is modified by your armour
# divided by 20
#

# PLATE ARMOUR
class Armour(Item):
    # requested for simpler inventory management
    pass

class Shields(Armour):
    type = 'shield'
    skill = 'shield'

class Shield(Shields):
    slot = 'left_hand'
    name = 'shield'
    label = 'Shield'
    description = 'It\'s the bottom of a barrel, but it does the job.'
    sprite_name = 'item-shield.png'
    armour_value = 1
    cost = 10
    weight = 8
add_item(Shield)

class Plate(Armour):
    type = 'plate'
    skill = 'armour'

class PlateChest(Plate):
    slot = 'chest'
    name = 'plate_chest'
    label = 'Plate chest armor'
    description = 'As solid as it gets'
    armour_value = 10
    cost = 100
    weight = 20
add_item(PlateChest)

class PlateLegs(Plate):
    slot = 'legs'
    type = 'plate'
    name = 'plate_legs'
    label = 'Plate leg armor'
    description = 'As solid as it gets'
    armour_value = 5
    cost = 90
    weight = 15
add_item(PlateLegs)

class PlateBoots(Plate):
    slot = 'feet'
    type = 'plate'
    name = 'plate_boots'
    label = 'Plate boots'
    description = 'Solid shit-kickers'
    armour_value = 2
    cost = 60
    weight = 5
add_item(PlateBoots)

class PlateGloves(Plate):
    slot = 'hands'
    type = 'plate'
    name = 'plate_gloves'
    label = 'Plate gloves'
    description = 'For when things need punching'
    armour_value = 3
    cost = 70
    weight = 4
add_item(PlateGloves)

# CHAIN MAIL
class ChainMail(Armour):
    type = 'mail'
    skill = 'armour'

class ChainMailTunic(ChainMail):
    slot = 'chest'
    name = 'chainmail_tunic'
    label = 'Chainmail tunic'
    description = 'For its benefits, this thing sure is heavy'
    armour_value = 5
    cost = 70
    weight = 10
add_item(ChainMailTunic)

class ChainMailKilt(ChainMail):
    slot = 'legs'
    name = 'chainmail_kilt'
    label = 'Chainmail kilt'
    description = 'At least it lets you move freely'
    armour_value = 2
    cost = 70
    weight = 8
add_item(ChainMailKilt)

class ChainMailBoots(ChainMail):
    slot = 'feet'
    name = 'chainmail_boots'
    label = 'Chainmail boots'
    description = 'The links hold together... Barely.'
    armour_value = 2
    cost = 40
    weight = 4
add_item(ChainMailBoots)

class ChainMailGloves(ChainMail):
    slot = 'hands'
    name = 'chainmail_gloves'
    label = 'Chainmail gloves'
    description = 'For when safety trumps comfort.'
    armour_value = 2
    cost = 45
    weight = 3
add_item(ChainMailGloves)


class Leather(Armour):
    type = 'leather'
    # no skill

class LeatherVest(Leather):
    slot = 'chest'
    name = 'leather_vest'
    label = 'Leather vest'
    description = 'Leather vest'
    armour_value = 3
    cost = 30
    weight = 5
add_item(LeatherVest)

class LeatherPants(Leather):
    slot = 'legs'
    name = 'leather_pants'
    label = 'Leather pants'
    description = 'Leather pants'
    armour_value = 1
    cost = 30
    weight = 3
add_item(LeatherPants)

class LeatherBoots(Leather):
    slot = 'feet'
    name = 'leather_boots'
    label = 'Leather boots'
    description = 'Leather boots'
    armour_value = 1
    cost = 20
    weight = 2
add_item(LeatherBoots)

class LeatherGloves(Leather):
    slot = 'hands'
    name = 'leather_gloves'
    label = 'Leather gloves'
    description = 'Leather gloves'
    armour_value = 1
    cost = 25
    weight = 2
add_item(LeatherGloves)

class Cloth(Armour):
    type = 'cloth'

class Vest(Cloth):
    slot = 'chest'
    name = 'cloth_vest'
    label = 'Cloth vest'
    description = 'Cloth vest'
    armour_value = 0
    cost = 10
    weight = 1
add_item(Vest)

class Pants(Cloth):
    slot = 'legs'
    name = 'cloth_pants'
    label = 'Cloth pants'
    description = 'Cloth pants'
    armour_value = 0
    cost = 10
    weight = 1
add_item(Pants)

class Shoes(Cloth):
    slot = 'feet'
    name = 'shoes'
    label = 'Shoes'
    description = 'Shoes'
    armour_value = 0
    cost = 5
    weight = 1
add_item(Shoes)

class Hat(Cloth):
    slot = 'head'
    name = 'hat'
    label = 'Hat'
    description = 'Hat'
    armour_value = 0
    cost = 2
    weight = 1
add_item(Hat)

# finally something for the wizards (+ the slippers perhaps)
class WizardHat(Cloth):
    slot = 'head'
    name = 'wizard hat'
    label = 'Wizard hat'
    description = '" ... and wizard hat "'
    armour_value = 0
    cost = 20
    weight = 1
add_item(WizardHat)

class Robe(Cloth):
    slot = 'chest'
    name = 'robe'
    label = 'Robe'
    description = '" I put on my robe ... "'
    armour_value = 1
    cost = 50
    weight = 5
add_item(Robe)


class Hide(Armour):
    type = 'hide'
    # no skill

class HideVest(Hide):
    slot = 'chest'
    name = 'hide_vest'
    label = 'Hide vest'
    description = 'Hide vest as worn by denizens. Of the dark places.'
    armour_value = 3
    cost = 5
    weight = 5
add_item(HideVest)


def random_shop(party):
    '''
    '''
    # XXX maybe also weight against stuff the party has
    default_shopsize = min(20, 5 + (party.level * 2))
    shop_items = []
    for i in range(1, default_shopsize):
        baseitem = random.choice(itemlist)
        low = min(max((party.level - 4),1), 4)
        high = min(party.level + 1, 5)
        q = random.randint(low, high) / 4.
        item = baseitem(q)
        shop_items.append(item)
    return shop_items

